package com.neuronbit.businessflow.intellij.editor.swing.editor;

import com.intellij.designer.DesignerEditorPanelFacade;
import com.intellij.designer.LightFillLayout;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ThreeComponentsSplitter;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vfs.VirtualFile;
import com.mxgraph.layout.hierarchical.mxHierarchicalLayout;
import com.mxgraph.layout.*;
import com.mxgraph.swing.handler.mxKeyboardHandler;
import com.mxgraph.swing.handler.mxRubberband;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.mxGraphOutline;
import com.mxgraph.swing.util.mxGraphActions;
import com.mxgraph.swing.util.mxMorphing;
import com.mxgraph.util.*;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.mxgraph.util.mxUndoableEdit.mxUndoableChange;
import com.mxgraph.view.mxGraph;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.List;

/**
 * The type Basic graph editor.
 */
public class BasicGraphEditor extends JPanel implements ModuleProvider, DesignerEditorPanelFacade {

    /**
     *
     */
    private static final long serialVersionUID = -6561623072112577140L;

    /**
     * Adds required resources for i18n
     */
    static {
        try {
            mxResources.add("com/mxgraph/examples/swing/resources/editor");
        } catch (Exception e) {
            // ignore
        }
    }

    /**
     * The Graph component.
     */
    protected mxGraphComponent graphComponent;

    /**
     * The Undo manager.
     */
    protected mxUndoManager undoManager;

    private final Project project;
    private final Module module;
    /**
     * The My file.
     */
    protected final VirtualFile myFile;
    /**
     * The App title.
     */
    protected String appTitle;

    /**
     * The Status bar.
     */
    protected JLabel statusBar;

    /**
     * The Current file.
     */
    protected File currentFile;

    /**
     * Flag indicating whether the current graph has been modified
     */
    protected boolean modified = false;

    /**
     * The Rubberband.
     */
    protected mxRubberband rubberband;

    /**
     * The Keyboard handler.
     */
    protected mxKeyboardHandler keyboardHandler;

    /**
     * The Undo handler.
     */
    protected mxIEventListener undoHandler = new mxIEventListener() {
        public void invoke(Object source, mxEventObject evt) {
            undoManager.undoableEditHappened((mxUndoableEdit) evt.getProperty("edit"));
        }
    };

    /**
     * The Change tracker.
     */
    protected mxIEventListener changeTracker = (source, evt) -> setModified(true);

    private final ThreeComponentsSplitter myContentSplitter = new ThreeComponentsSplitter();

    /**
     * The Wheel tracker.
     */
// Installs mouse wheel listener for zooming
    protected MouseWheelListener wheelTracker = new MouseWheelListener() {
        /**
         *
         */
        public void mouseWheelMoved(MouseWheelEvent e) {
            if (e.getSource() instanceof mxGraphOutline
                    || e.isControlDown()) {
                BasicGraphEditor.this.mouseWheelMoved(e);
            }
        }

    };


    /**
     * Instantiates a new Basic graph editor.
     *
     * @param project   the project
     * @param module    the module
     * @param file      the file
     * @param appTitle  the app title
     * @param component the component
     */
    public BasicGraphEditor(@NotNull Project project, Module module, VirtualFile file, String appTitle, mxGraphComponent component) {
        this.project = project;
        this.module = module;
        this.myFile = file;
        // Stores and updates the frame title
        this.appTitle = appTitle;

        // Stores a reference to the graph and creates the command history
        graphComponent = component;
        final mxGraph graph = graphComponent.getGraph();
        undoManager = createUndoManager();

        // Do not change the scale and translation after files have been loaded
        graph.setResetViewOnRootChange(false);

        // Updates the modified flag if the graph model changes
        graph.getModel().addListener(mxEvent.CHANGE, changeTracker);

        // Adds the command history to the model and view
        graph.getModel().addListener(mxEvent.UNDO, undoHandler);
        graph.getView().addListener(mxEvent.UNDO, undoHandler);

        // Keeps the selection in sync with the command history
        mxIEventListener undoHandler = (source, evt) -> {
            List<mxUndoableChange> changes = ((mxUndoableEdit) evt.getProperty("edit")).getChanges();
            graph.setSelectionCells(graph.getSelectionCellsForChanges(changes));
        };

        undoManager.addListener(mxEvent.UNDO, undoHandler);
        undoManager.addListener(mxEvent.REDO, undoHandler);

//        // Creates the inner split pane that contains the library with the
//        // palettes and the graph outline on the left side of the window
//        JSplitPane inner = new JSplitPane(JSplitPane.VERTICAL_SPLIT, libraryPane, graphOutline);
//        inner.setDividerLocation(620);
//        inner.setResizeWeight(1);
//        inner.setDividerSize(6);
//        inner.setBorder(null);


//        JSplitPane right = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, graphComponent, propertyPane);
//        right.setOneTouchExpandable(true);
//        right.setDividerLocation(0.5);
//        right.setResizeWeight(1);
//        right.setDividerSize(6);
//        right.setBorder(null);

        // Creates the outer split pane that contains the inner split pane and
        // the graph component on the right side of the window
//        JSplitPane outer = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, inner, right);
//        outer.setOneTouchExpandable(true);
//        outer.setDividerLocation(200);
//        outer.setDividerSize(6);
//        outer.setBorder(null);

        // Creates the status bar
        statusBar = createStatusBar();

        // Display some useful information about repaint events
        installRepaintListener();

        // Puts everything together
        setLayout(new BorderLayout());
        //add(outer, BorderLayout.CENTER);
        myContentSplitter.setDividerWidth(0);
        myContentSplitter.setDividerMouseZoneSize(Registry.intValue("ide.splitter.mouseZone"));
        add(myContentSplitter, BorderLayout.CENTER);
        add(statusBar, BorderLayout.SOUTH);

        JPanel contentPanel = new JPanel(new LightFillLayout());
        //JLabel toolbar = new JLabel();
        //toolbar.setVisible(false);
        contentPanel.add(new EditorToolBar(this, JToolBar.HORIZONTAL));
        contentPanel.add(graphComponent);
        //
        myContentSplitter.setInnerComponent(contentPanel);

        // Installs rubberband selection and handling for some special
        // keystrokes such as F2, Control-C, -V, X, A etc.
        installHandlers();
        installListeners();
        updateTitle();
    }

    /**
     * Create undo manager mx undo manager.
     *
     * @return the mx undo manager
     */
    protected mxUndoManager createUndoManager() {
        return new mxUndoManager();
    }

    /**
     * Install handlers.
     */
    protected void installHandlers() {
        rubberband = new mxRubberband(graphComponent);
        keyboardHandler = new EditorKeyboardHandler(graphComponent);
    }

    /**
     * Create status bar j label.
     *
     * @return the j label
     */
    protected JLabel createStatusBar() {
        JLabel statusBar = new JLabel(mxResources.get("ready"));
        statusBar.setBorder(BorderFactory.createEmptyBorder(2, 4, 2, 4));

        return statusBar;
    }

    /**
     * Install repaint listener.
     */
    protected void installRepaintListener() {
        graphComponent.getGraph().addListener(mxEvent.REPAINT,
                new mxIEventListener() {
                    public void invoke(Object source, mxEventObject evt) {
                        String buffer = (graphComponent.getTripleBuffer() != null) ? ""
                                : " (unbuffered)";
                        mxRectangle dirty = (mxRectangle) evt
                                .getProperty("region");

                        if (dirty == null) {
                            status("Repaint all" + buffer);
                        } else {
                            status("Repaint: x=" + (int) (dirty.getX()) + " y="
                                    + (int) (dirty.getY()) + " w="
                                    + (int) (dirty.getWidth()) + " h="
                                    + (int) (dirty.getHeight()) + buffer);
                        }
                    }
                });
    }

    /**
     * Gets wheel tracker.
     *
     * @return the wheel tracker
     */
    public MouseWheelListener getWheelTracker() {
        return wheelTracker;
    }

    //    /**
//     * Insert basic property property inspector.
//     *
//     * @param title the title
//     * @return the property inspector
//     */
//    public PropertyInspector insertBasicProperty(String title) {
//
////        final JBScrollPane scrollPane = new JBScrollPane(inspector);
////        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
////        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
//
//
//        // Updates the widths of the palettes if the container size changes
////        propertyPane.addComponentListener(new ComponentAdapter() {
////            /**
////             *
////             */
////            public void componentResized(ComponentEvent e) {
////                int w = scrollPane.getWidth() - scrollPane.getVerticalScrollBar().getWidth();
////                inspector.setPreferredWidth(w);
////            }
////
////        });
//        final BasicGraphEditor self = this;
//
//
//
//        return inspector;
//    }

    /**
     * Mouse wheel moved.
     *
     * @param e the e
     */
    protected void mouseWheelMoved(MouseWheelEvent e) {
        if (e.getWheelRotation() < 0) {
            graphComponent.zoomIn();
        } else {
            graphComponent.zoomOut();
        }

        status(mxResources.get("scale") + ": "
                + (int) (100 * graphComponent.getGraph().getView().getScale())
                + "%");
    }

    /**
     * Show graph popup menu.
     *
     * @param e the e
     */
    protected void showGraphPopupMenu(MouseEvent e) {
        Point pt = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(),
                graphComponent);
        EditorPopupMenu menu = new EditorPopupMenu(BasicGraphEditor.this);
        menu.show(graphComponent, pt.x, pt.y);

        e.consume();
    }

    /**
     * Mouse location changed.
     *
     * @param e the e
     */
    protected void mouseLocationChanged(MouseEvent e) {
        status(e.getX() + ", " + e.getY());
    }

    /**
     * Install listeners.
     */
    protected void installListeners() {
        // Installs the popup menu in the graph component
        graphComponent.getGraphControl().addMouseListener(new MouseAdapter() {

            /**
             *
             */
            public void mousePressed(MouseEvent e) {
                // Handles context menu on the Mac where the trigger is on mousepressed
                //mouseReleased(e);
            }

            /**
             *
             */
            public void mouseReleased(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    // Handles context menu on the Windows where the trigger is on mousepressed
                    //showGraphPopupMenu(e);
                }
            }

        });

        // Installs a mouse motion listener to display the mouse location
        graphComponent.getGraphControl().addMouseMotionListener(
                new MouseMotionListener() {

                    /*
                     * (non-Javadoc)
                     * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
                     */
                    public void mouseDragged(MouseEvent e) {
                        mouseLocationChanged(e);
                    }

                    /*
                     * (non-Javadoc)
                     * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
                     */
                    public void mouseMoved(MouseEvent e) {
                        mouseDragged(e);
                    }

                });

        MouseAdapter dragScrollHandler = getDragScrollHandler();
        graphComponent.getGraphControl().addMouseListener(dragScrollHandler);
        graphComponent.getGraphControl().addMouseMotionListener(dragScrollHandler);
    }


    private MouseAdapter getDragScrollHandler() {
        return new MouseAdapter() {

            protected Point start = null;

            public void mousePressed(MouseEvent e) {
                if (e.getButton() == MouseEvent.BUTTON3) {
                    start = e.getPoint();
                }
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                start = null;
            }

            public void mouseDragged(MouseEvent e) {
                if (isEnabled() && start != null) {
                    int dx = (int) ((e.getX() - start.getX()) / 1.25);
                    int dy = (int) ((e.getY() - start.getY()) / 1.25);

                    // Keeps current location as start for delta movement
                    // of the scrollbars
                    start = e.getPoint();

                    graphComponent.getHorizontalScrollBar().setValue(
                            graphComponent.getHorizontalScrollBar().getValue()
                                    - dx);
                    graphComponent.getVerticalScrollBar().setValue(
                            graphComponent.getVerticalScrollBar().getValue()
                                    - dy);
                }

            }
        };
    }

    /**
     * Sets current file.
     *
     * @param file the file
     */
    public void setCurrentFile(File file) {
        File oldValue = currentFile;
        currentFile = file;

        firePropertyChange("currentFile", oldValue, file);

        if (oldValue != file) {
            updateTitle();
        }
    }

    /**
     * Gets current file.
     *
     * @return the current file
     */
    public File getCurrentFile() {
        return currentFile;
    }

    /**
     * Sets modified.
     *
     * @param modified the modified
     */
    public void setModified(boolean modified) {
        boolean oldValue = this.modified;
        this.modified = modified;

        firePropertyChange("modified", oldValue, modified);

        if (oldValue != modified) {
            updateTitle();
        }
    }

    /**
     * Is modified boolean.
     *
     * @return whether or not the current graph has been modified
     */
    public boolean isModified() {
        return modified;
    }

    /**
     * Gets graph component.
     *
     * @return the graph component
     */
    public mxGraphComponent getGraphComponent() {
        return graphComponent;
    }


    /**
     * Gets undo manager.
     *
     * @return the undo manager
     */
    public mxUndoManager getUndoManager() {
        return undoManager;
    }

    /**
     * Bind action.
     *
     * @param name   the name
     * @param action the action
     * @return a new Action bound to the specified string name
     */
    public Action bind(String name, final Action action) {
        return bind(name, action, null);
    }

    /**
     * Bind action.
     *
     * @param name    the name
     * @param action  the action
     * @param iconUrl the icon url
     * @return a new Action bound to the specified string name and icon
     */
    @SuppressWarnings("serial")
    public Action bind(String name, final Action action, String iconUrl) {
        ImageIcon imageIcon = null;
        if (iconUrl != null) {
            imageIcon = new ImageIcon(BasicGraphEditor.class.getResource(iconUrl));

        }
        AbstractAction newAction = new AbstractAction(name, imageIcon) {
            public void actionPerformed(ActionEvent e) {
                action.actionPerformed(new ActionEvent(getGraphComponent(), e
                        .getID(), e.getActionCommand()));
            }
        };

        newAction.putValue(Action.SHORT_DESCRIPTION, action.getValue(Action.SHORT_DESCRIPTION));
        return newAction;
    }


    public JToggleButton bind(mxGraphActions.ConnectableAction action, String iconUrl, String selectIconUrl) {
        JToggleButton toggleButton = new JToggleButton();
        toggleButton.setIcon(new ImageIcon(BasicGraphEditor.class.getResource(iconUrl)));
        ImageIcon selectedIcon = new ImageIcon(BasicGraphEditor.class.getResource(selectIconUrl));
        Image img = selectedIcon.getImage();
        Image newimg = img.getScaledInstance(16, 16,  java.awt.Image.SCALE_SMOOTH);
        toggleButton.setSelectedIcon(new ImageIcon(newimg));
        // itemStateChanged() method is invoked automatically
        toggleButton.addItemListener(itemEvent -> {
            // event is generated in button
            int state = itemEvent.getStateChange();

            // if selected print selected in console
            getGraphComponent().setConnectable(state == ItemEvent.SELECTED);
        });
        action.addToggleButton(toggleButton);

       return toggleButton;
    }

    /**
     * Status.
     *
     * @param msg the msg
     */
    public void status(String msg) {
        statusBar.setText(msg);
    }

    /**
     * Update title.
     */
    public void updateTitle() {
        JFrame frame = (JFrame) SwingUtilities.windowForComponent(this);

        if (frame != null) {
            String title = (currentFile != null) ? currentFile
                    .getAbsolutePath() : mxResources.get("newDiagram");

            if (modified) {
                title += "*";
            }

            frame.setTitle(title + " - " + appTitle);
        }
    }

    /**
     * About.
     */
    public void about() {
        JFrame frame = (JFrame) SwingUtilities.windowForComponent(this);

        if (frame != null) {
            EditorAboutFrame about = new EditorAboutFrame(frame);
            about.setModal(true);

            // Centers inside the application frame
            int x = frame.getX() + (frame.getWidth() - about.getWidth()) / 2;
            int y = frame.getY() + (frame.getHeight() - about.getHeight()) / 2;
            about.setLocation(x, y);

            // Shows the modal dialog and waits
            about.setVisible(true);
        }
    }

    /**
     * Exit.
     */
    public void exit() {
        JFrame frame = (JFrame) SwingUtilities.windowForComponent(this);

        if (frame != null) {
            frame.dispose();
        }
    }

    /**
     * Sets look and feel.
     *
     * @param clazz the clazz
     */
    public void setLookAndFeel(String clazz) {
        JFrame frame = (JFrame) SwingUtilities.windowForComponent(this);

        if (frame != null) {
            try {
                UIManager.setLookAndFeel(clazz);
                SwingUtilities.updateComponentTreeUI(frame);

                // Needs to assign the key bindings again
                keyboardHandler = new EditorKeyboardHandler(graphComponent);
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
    }

    /**
     * Reset.
     */
    public void reset() {
        setModified(false);
        getUndoManager().clear();
        getGraphComponent().zoomAndCenter();
    }

//    /**
//     *
//     */
//    public JFrame createFrame(JMenuBar menuBar) {
//        JFrame frame = new JFrame();
//        frame.getContentPane().add(this);
//        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//        frame.setJMenuBar(menuBar);
//        frame.setSize(870, 640);
//
//        // Updates the frame title
//        updateTitle();
//
//        return frame;
//    }

    /**
     * Creates an action that executes the specified layout.
     *
     * @param key     Key to be used for getting the label from mxResources and also            to create the layout instance for the commercial graph editor example.
     * @param animate the animate
     * @return an action that executes the specified layout
     */
    @SuppressWarnings("serial")
    public Action graphLayout(final String key, boolean animate) {
        final mxIGraphLayout layout = createLayout(key, animate);

        if (layout != null) {
            return new AbstractAction(mxResources.get(key)) {
                public void actionPerformed(ActionEvent e) {
                    final mxGraph graph = graphComponent.getGraph();
                    Object cell = graph.getSelectionCell();

                    if (cell == null
                            || graph.getModel().getChildCount(cell) == 0) {
                        cell = graph.getDefaultParent();
                    }

                    graph.getModel().beginUpdate();
                    try {
                        long t0 = System.currentTimeMillis();
                        layout.execute(cell);
                        status("Layout: " + (System.currentTimeMillis() - t0)
                                + " ms");
                    } finally {
                        mxMorphing morph = new mxMorphing(graphComponent, 20,
                                1.2, 20);

                        morph.addListener(mxEvent.DONE, new mxIEventListener() {

                            public void invoke(Object sender, mxEventObject evt) {
                                graph.getModel().endUpdate();
                            }

                        });

                        morph.startAnimation();
                    }

                }

            };
        } else {
            return new AbstractAction(mxResources.get(key)) {

                public void actionPerformed(ActionEvent e) {
                    JOptionPane.showMessageDialog(graphComponent,
                            mxResources.get("noLayout"));
                }

            };
        }
    }

    /**
     * Creates a layout instance for the given identifier.
     *
     * @param ident   the ident
     * @param animate the animate
     * @return the mx i graph layout
     */
    protected mxIGraphLayout createLayout(String ident, boolean animate) {
        mxIGraphLayout layout = null;

        if (ident != null) {
            mxGraph graph = graphComponent.getGraph();

            if (ident.equals("verticalHierarchical")) {
                layout = new mxHierarchicalLayout(graph);
            } else if (ident.equals("horizontalHierarchical")) {
                layout = new mxHierarchicalLayout(graph, JLabel.WEST);
            } else if (ident.equals("verticalTree")) {
                layout = new mxCompactTreeLayout(graph, false);
            } else if (ident.equals("horizontalTree")) {
                layout = new mxCompactTreeLayout(graph, true);
            } else if (ident.equals("parallelEdges")) {
                layout = new mxParallelEdgeLayout(graph);
            } else if (ident.equals("placeEdgeLabels")) {
                layout = new mxEdgeLabelLayout(graph);
            } else if (ident.equals("organicLayout")) {
                layout = new mxOrganicLayout(graph);
            }
            if (ident.equals("verticalPartition")) {
                layout = new mxPartitionLayout(graph, false) {
                    /**
                     * Overrides the empty implementation to return the size of the
                     * graph control.
                     */
                    public mxRectangle getContainerSize() {
                        return graphComponent.getLayoutAreaSize();
                    }
                };
            } else if (ident.equals("horizontalPartition")) {
                layout = new mxPartitionLayout(graph, true) {
                    /**
                     * Overrides the empty implementation to return the size of the
                     * graph control.
                     */
                    public mxRectangle getContainerSize() {
                        return graphComponent.getLayoutAreaSize();
                    }
                };
            } else if (ident.equals("verticalStack")) {
                layout = new mxStackLayout(graph, false) {
                    /**
                     * Overrides the empty implementation to return the size of the
                     * graph control.
                     */
                    public mxRectangle getContainerSize() {
                        return graphComponent.getLayoutAreaSize();
                    }
                };
            } else if (ident.equals("horizontalStack")) {
                layout = new mxStackLayout(graph, true) {
                    /**
                     * Overrides the empty implementation to return the size of the
                     * graph control.
                     */
                    public mxRectangle getContainerSize() {
                        return graphComponent.getLayoutAreaSize();
                    }
                };
            } else if (ident.equals("circleLayout")) {
                layout = new mxCircleLayout(graph);
            }
        }

        return layout;
    }

    public Project getProject() {
        return this.project;
    }

    /**
     * Gets my file.
     *
     * @return the my file
     */
    public VirtualFile getMyFile() {
        return this.myFile;
    }

    public Module getModule() {
        return module;
    }

    @Override
    public ThreeComponentsSplitter getContentSplitter() {
        return this.myContentSplitter;
    }
}
